package cn.edu.zufe.mjt.spinrg;

import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author 蒙锦涛
 * @date 2025/4/24
 * @description 开始手写Spring框架
 */

// 应用上下文类，负责管理Bean的生命周期和依赖注入
public class ApplicationContext {

    // 容器，用于存储Bean实例
    private final Map<String, Object> ioc = new HashMap<>();

    private final Map<String, Object> loadingIoc = new HashMap<>();
    // 存储Bean定义的映射
    private final Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();
    private final List<BeanPostProcessor> postProcessors = new ArrayList<>();

    // 构造函数，初始化应用上下文时传入包名，扫描并创建Bean
    public ApplicationContext(String packageName) throws Exception {
        initContext(packageName);
    }

    /**
     * 初始化上下文，扫描指定包并创建Bean
     *
     * @param packageName 需要扫描的包名
     */
    public void initContext(String packageName) throws Exception {
        // 扫描指定包下的所有类文件，先加载 BeanDefinition
        scanPackage(packageName).stream().filter(this::canCreate).forEach(this::wrapper);

        initBeanPostProcessor();

        // 再创建 Bean
        beanDefinitionMap.values().forEach(this::createBean);
    }

    private void initBeanPostProcessor() {
        beanDefinitionMap.values().stream()
                .filter(bd -> BeanPostProcessor.class.isAssignableFrom(bd.getBeanType()))
                .map(this::createBean).map((bean) -> (BeanPostProcessor) bean)
                .forEach(postProcessors::add);
    }

    /**
     * 判断类是否需要被创建为Bean，即是否被@Component注解标记
     *
     * @param type 待判断的类
     * @return 如果类需要被创建为Bean，则返回true，否则返回false
     */
    protected boolean canCreate(Class<?> type) {
        return type.isAnnotationPresent(Component.class);
    }

    /**
     * 创建Bean实例并放入容器中
     *
     * @param beanDefinition Bean的定义
     */
    protected Object createBean(BeanDefinition beanDefinition) {
        String name = beanDefinition.getName();
        if (ioc.containsKey(name)) {
            return ioc.get(name);
        }
        if (loadingIoc.containsKey(name)) {
            return loadingIoc.get(name);
        }
        return doCreateBean(beanDefinition);
    }

    /**
     * 实际创建Bean实例的过程
     *
     * @param beanDefinition Bean的定义
     */
    private Object doCreateBean(BeanDefinition beanDefinition) {
        Constructor<?> constructor = beanDefinition.getConstructor();
        Object bean;
        try {
            // 实例化这个Bean
            bean = constructor.newInstance();
            // 装入正在加载的 Bean
            loadingIoc.put(beanDefinition.getName(), bean);
            // 依赖注入
            autoWiredBean(bean, beanDefinition);
            bean = initializingBean(bean, beanDefinition);
            loadingIoc.remove(beanDefinition.getName());
            ioc.put(beanDefinition.getName(), bean);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return bean;
    }

    /**
     * 初始化 Bean 实例
     */
    private Object initializingBean(Object bean, BeanDefinition beanDefinition) throws InvocationTargetException, IllegalAccessException {

        // 调用 BeanPostProcessor 的 beforeInitializeBean 方法
        for (BeanPostProcessor postProcessor : postProcessors) {
            bean = postProcessor.beforeInitializeBean(bean, beanDefinition.getName());
        }

        // 如果 BeanDefinition 中的 postConstructMethod 不为空，则调用该方法
        // 该方法用于在 Bean 创建完成后执行一些初始化操作
        Method postConstructMethod = beanDefinition.getPostConstructMethod();
        if (postConstructMethod != null) {
            postConstructMethod.invoke(bean);
        }

        // 调用 BeanPostProcessor 的 afterInitializeBean 方法
        for (BeanPostProcessor postProcessor : postProcessors) {
            bean = postProcessor.afterInitializeBean(bean, beanDefinition.getName());
        }

        return bean;
    }

    private void autoWiredBean(Object bean, BeanDefinition beanDefinition) throws IllegalAccessException {
        for (Field autowiredField : beanDefinition.getAutowiredFields()) {
            autowiredField.setAccessible(true);
            autowiredField.set(bean, getBean(autowiredField.getType()));
        }
    }

    /**
     * 将类包装成BeanDefinition对象
     *
     * @param type 需要包装的类
     * @return 包装后的BeanDefinition对象
     */
    protected BeanDefinition wrapper(Class<?> type) {
        BeanDefinition beanDefinition = new BeanDefinition(type);
        if (beanDefinitionMap.containsKey(beanDefinition.getName())) {
            throw new RuntimeException("Bean 名称重复: " + beanDefinition.getName());
        }
        beanDefinitionMap.put(beanDefinition.getName(), beanDefinition);
        return beanDefinition;
    }

    /**
     * 扫描指定包下的所有类文件
     *
     * @param packageName 需要扫描的包名
     * @return 扫描到的类列表
     */
    private List<Class<?>> scanPackage(String packageName) throws Exception {
        List<Class<?>> classList = new ArrayList<>();

        // 将包名转换为文件路径格式
        URL resource = this.getClass().getClassLoader().getResource(packageName.replace(".", File.separator));
        if (resource == null) {
            throw new IllegalArgumentException("Package not found: " + packageName);
        }
        // 处理路径中的非法字符
        String filePath = resource.getFile();
        if (filePath.startsWith("/")) { // 如果路径以 / 开头（Windows 特有）
            filePath = filePath.substring(1); // 去掉开头的 /
        }

        // 转换为 Path 对象，使用URLDecoder解码以避免编码问题
        Path path = Paths.get(URLDecoder.decode(filePath, StandardCharsets.UTF_8));

        // 遍历指定路径下的所有文件
        Files.walkFileTree(path, new SimpleFileVisitor<>() {
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
                String absolutePath = file.toAbsolutePath().toString();
                if (absolutePath.endsWith(".class")) {
                    // 将文件路径转换为类名
                    String replaceStr = absolutePath.replace(File.separator, ".");
                    int packageIndex = replaceStr.indexOf(packageName);
                    String className = replaceStr.substring(packageIndex, replaceStr.length() - ".class".length());
                    try {
                        classList.add(Class.forName(className));
                    } catch (ClassNotFoundException e) {
                        throw new RuntimeException(e);
                    }
                }
                return FileVisitResult.CONTINUE; // 继续遍历
            }
        });
        return classList;
    }

    /**
     * 根据名称获取Bean实例
     *
     * @param name Bean的名称
     * @return Bean实例
     */
    public Object getBean(String name) {
        if (name == null) {
            return null;
        }

        Object bean = this.ioc.get(name);
        if (bean != null) {
            return bean;
        }
        // 如果Bean不存在，那怎么知道是没有这个Bean还是没有加载？
        // 这时候只需要知道是否有这个 BeanDefinition 就可以了
        if (beanDefinitionMap.containsKey(name)) {
            return createBean(beanDefinitionMap.get(name));
        }
        return null;
    }

    /**
     * 根据类型获取Bean实例，返回第一个匹配的实例
     *
     * @param beanType Bean的类型
     * @param <T>      泛型参数
     * @return 第一个匹配的Bean实例，如果不存在则返回null
     */
    public <T> T getBean(Class<T> beanType) {
        String beanName = this.beanDefinitionMap.values().stream()
                .filter(bd -> beanType.isAssignableFrom(bd.getBeanType()))
                .map(BeanDefinition::getName)
                .findFirst()
                .orElse(null);
        return (T) getBean(beanName);
    }

    /**
     * 根据类型获取所有匹配的Bean实例
     *
     * @param beanType Bean的类型
     * @param <T>      泛型参数
     * @return 所有匹配的Bean实例列表
     */
    public <T> List<T> getBeans(Class<T> beanType) {
        return this.beanDefinitionMap.values().stream()
                .filter(bd -> beanType.isAssignableFrom(bd.getBeanType()))
                .map(BeanDefinition::getName)
                .map(this::getBean)
                .map(bean -> (T) bean)
                .toList();
    }

}
